package com.jl.card.provider.jl3g;

import java.util.Arrays;
import java.util.Iterator;

import com.jl.card.Card;
import com.jl.card.ErrorProcess;
import com.jl.card.ISOConstants;
import com.jl.card.SWException;
import com.jl.card.file.CardFilePath;
import com.jl.card.file.IFileAccess;
import com.jl.card.file.IFileInfo;
import com.jl.card.filesystem.EFarr;
import com.jl.card.filesystem.EFarrFactory;
import com.jl.card.filesystem.IEF;
import com.jl.card.parse.SimpleAnalyse;
import com.jl.card.util.APDUUtil;
import com.jl.util.HexUtil;

public class JL3GFileAccess implements IFileAccess {

	public final static byte MODE_ABS = 0x04;
	public final static byte MODE_PRE = 0x03;
	public final static byte MODE_NEXT = 0x02;

	public final static byte cla = (byte) 0x00;

	private CardFilePath path;
	private Card card;

	private JL3GFileInfo info;

	public JL3GFileAccess(CardFilePath path, Card card) {
		this.path = path;
		this.card = card;
	}

	@Override
	public IFileInfo select() throws SWException {
		info = new JL3GFileInfo(internalSelect());
		if (EFarrFactory.containKey(card, info.getEFarrID())) {
			IEF efarr = EFarrFactory.getEFarr(card, info.getEFarrID());
			info.setEFarr(efarr);
		} else {
			IEF efarr = EFarrFactory.getEFarr(card, info.getEFarrID());
			info.setEFarr(efarr);
			internalSelect();
		}
		return info;
	}

	@Override
	public byte[] readBinary(int offset, int length) throws SWException {
		if (offset + length > info.getLength()) {
			ErrorProcess
					.throwRuntimeException("offset + length is bigger than file length");
		}
		byte hi = HexUtil.highByte((short) (offset & 0xFFFF));
		byte lo = HexUtil.lowByte((short) (offset & 0xFFFF));
		byte[] cmd = APDUUtil.make(cla, (byte) 0xb0, hi, lo,
				(byte) (length & 0xFF));
		byte[] res = card.plainAPDU(cmd);
		int sw = SimpleAnalyse.sw(res);
		if (sw != ISOConstants.RC_OK) {
			ErrorProcess.throwSWException(sw);
		}
		return SimpleAnalyse.data(res);
	}

	@Override
	public void updateBinary(int offset, byte[] data, int dataOffset,
			int dataLength) throws SWException {
		if (offset + dataLength > info.getLength()) {
			ErrorProcess
					.throwRuntimeException("offset + dataLength is bigger than file length");
		}
		byte hi = HexUtil.highByte((short) (offset & 0xFFFF));
		byte lo = HexUtil.lowByte((short) (offset & 0xFFFF));
		byte len = (byte) (dataLength & 0xFF);
		byte[] _data = Arrays.copyOfRange(data, dataOffset, len);
		byte[] cmd = APDUUtil.make(cla, (byte) 0xd6, hi, lo, _data);
		byte[] res = card.plainAPDU(cmd);
		int sw = SimpleAnalyse.sw(res);
		if (sw != ISOConstants.RC_OK) {
			ErrorProcess.throwSWException(sw);
		}
	}

	@Override
	public byte[] readRecord(int recNo) throws SWException {
		if (recNo <= 0 || recNo > info.getLength() / info.getRecordSize()) {
			ErrorProcess.throwRuntimeException("the record number is wrong");
		}
		byte len = (byte) (info.getRecordSize() & 0xFF);
		byte[] cmd = APDUUtil.make(cla, (byte) 0xb2, (byte) (recNo & 0xFF),
				MODE_ABS, len);
		byte[] res = card.plainAPDU(cmd);
		int sw = SimpleAnalyse.sw(res);
		if (sw != ISOConstants.RC_OK) {
			ErrorProcess.throwSWException(sw);
		}
		return SimpleAnalyse.data(res);
	}

	@Override
	public void updateRecord(int recNo, byte[] data, int dataOffset)
			throws SWException {
		if (recNo <= 0 || recNo > info.getLength() / info.getRecordSize()) {
			ErrorProcess.throwRuntimeException("the record number is wrong");
		}
		if (data.length - dataOffset < info.getRecordSize()) {
			ErrorProcess
					.throwRuntimeException("not enough data to update record");
		}
		byte[] _data = Arrays.copyOfRange(data, dataOffset,
				info.getRecordSize());
		byte mode = MODE_ABS;
		if (info.isCyclic()) {
			mode = MODE_PRE;
		}
		byte[] cmd = APDUUtil.make(cla, (byte) 0xdc, (byte) (recNo & 0xFF),
				mode, _data);
		byte[] res = card.plainAPDU(cmd);
		int sw = SimpleAnalyse.sw(res);
		if (sw != ISOConstants.RC_OK) {
			ErrorProcess.throwSWException(sw);
		}
	}

	@Override
	public byte[] read(int offset, int length) throws SWException {
		if (info.isDirectory()) {
			throw new UnsupportedOperationException("can't read from Directory");
		}
		byte[] all = new byte[length];
		int cur = offset;
		if (info.isTransparent()) {
			int readLen = 0;
			while (cur < length) {
				if (length - cur > 0xFF) {
					readLen = 0xFF;
				} else {
					readLen = length - cur;
				}
				byte[] temp = this.readBinary(cur, readLen);
				System.arraycopy(temp, 0, all, cur, temp.length);
				cur += readLen;
			}
		} else {
			
		}
		return all;
	}

	@Override
	public void write(int offset, byte[] data, int dataOffset, int dataLength) {
		// TODO Auto-generated method stub
	}

	public synchronized IEF intiEFarr() throws SWException {
		info = new JL3GFileInfo(internalSelect());
		byte[][] all = new byte[info.getLength() / info.getRecordSize()][];

		for (int i = 1, end = info.getLength() / info.getRecordSize(); i <= end; i++) {
			all[i - 1] = this.readRecord(i);
		}
		EFarr arr = new EFarr(all);
		return arr;
	}

	private byte[] internalSelect() throws SWException {
		Iterator<String> it = path.iterator();
		byte[] res = null;
		while (it.hasNext()) {
			String filename = it.next();
			byte[] name = HexUtil.parseHexToByteArray(filename);
			byte[] cmd = APDUUtil.make(cla, (byte) 0xa4, (byte) 0x00,
					(byte) 0x04, name);
			res = card.autoAPDU(cmd);
		}
		int sw = SimpleAnalyse.sw(res);
		byte[] fci = SimpleAnalyse.data(res);
		if (sw != ISOConstants.RC_OK) {
			ErrorProcess.throwSWException(sw);
		}
		return fci;
	}

}
